using System;
using System.Collections;
using System.Collections.Generic;
using static LightCAD.MathLib.Constants;

namespace LightCAD.MathLib
{
    public class CubicInterpolant : Interpolant
    {
        #region Properties

        private double _weightPrev;
        private int _offsetPrev;
        private double _weightNext;
        private int _offsetNext;

        #endregion

        #region constructor
        public CubicInterpolant(double[] parameterPositions, double[] sampleValues, int sampleSize, double[] resultBuffer = null)
            : base(parameterPositions, sampleValues, sampleSize, resultBuffer)
        {
            ;
            this._weightPrev = -0;
            this._offsetPrev = -0;
            this._weightNext = -0;
            this._offsetNext = -0;
            this.DefaultSettings_ = new InterpolantSettings
            {
                EndingStart = ZeroCurvatureEnding,
                EndingEnd = ZeroCurvatureEnding

            };
        }
        #endregion

        #region methods
        public override void IntervalChanged_(int i1, double t0, double t1)
        {
            var pp = this.ParameterPositions;
            int iPrev = i1 - 2,
                iNext = i1 + 1;
            double tPrev = pp[iPrev],
                   tNext = pp[iNext];
            if (double.IsNaN(tPrev))
            {
                switch (this.GetSettings_().EndingStart)
                {
                    case ZeroSlopeEnding:
                        // f"(t0) = 0
                        iPrev = i1;
                        tPrev = 2 * t0 - t1;
                        break;
                    case WrapAroundEnding:
                        // use the other end of the curve
                        iPrev = pp.Length - 2;
                        tPrev = t0 + pp[iPrev] - pp[iPrev + 1];
                        break;
                    default: // ZeroCurvatureEnding
                             // f""(t0) = 0 a.k.a. Natural Spline
                        iPrev = i1;
                        tPrev = t1;
                        break;
                }
            }
            if (double.IsNaN(tNext))
            {
                switch (this.GetSettings_().EndingEnd)
                {
                    case ZeroSlopeEnding:
                        // f"(tN) = 0
                        iNext = i1;
                        tNext = 2 * t1 - t0;
                        break;
                    case WrapAroundEnding:
                        // use the other end of the curve
                        iNext = 1;
                        tNext = t1 + pp[1] - pp[0];
                        break;
                    default: // ZeroCurvatureEnding
                             // f""(tN) = 0, a.k.a. Natural Spline
                        iNext = i1 - 1;
                        tNext = t0;
                        break;
                }
            }
            double halfDt = (t1 - t0) * 0.5;
            int stride = this.ValueSize;
            this._weightPrev = halfDt / (t0 - tPrev);
            this._weightNext = halfDt / (tNext - t1);
            this._offsetPrev = iPrev * stride;
            this._offsetNext = iNext * stride;
        }
        public override double[] Interpolate_(int i1, double t0, double t, double t1)
        {
            double[] result = this.ResultBuffer,
                values = this.SampleValues;
            int stride = this.ValueSize;
            int o1 = i1 * stride, o0 = o1 - stride;
            int oP = this._offsetPrev, oN = this._offsetNext;
            double wP = this._weightPrev, wN = this._weightNext,
                p = (t - t0) / (t1 - t0),
                pp = p * p,
                ppp = pp * p;
            // evaluate polynomials
            var sP = -wP * ppp + 2 * wP * pp - wP * p;
            var s0 = (1 + wP) * ppp + (-1.5 - 2 * wP) * pp + (-0.5 + wP) * p + 1;
            var s1 = (-1 - wN) * ppp + (1.5 + wN) * pp + 0.5 * p;
            var sN = wN * ppp - wN * pp;
            // combine data linearly
            for (int i = 0; i != stride; ++i)
            {
                result[i] =
                        sP * values[oP + i] +
                        s0 * values[o0 + i] +
                        s1 * values[o1 + i] +
                        sN * values[oN + i];
            }
            return result;
        }
        #endregion

    }
}
